let editor;
let editingBlock;
let pageBrakeState = 0;
let blockContext = undefined;
let insertingInBlock = false;
const appender = document.createElement('input');
appender.className = 'editor-input';
appender.id = 'editor-input';
appender.addEventListener('keydown', onAppenderType);
const fixer = document.createElement('input');
fixer.className = 'editor-input';
fixer.id = 'editor-amend';
fixer.addEventListener('keydown', onFixerType);

function onElementDoubleClick(e) {
	// TODO
	console.log(e);
}

function startBlock(name, param, line) {
	let out;
	switch (name) {
		case 'code':
			out = document.createElement('code');
			blockContext = {
				type: 'code',
				language: param
			};
			break;
		case 'img':
			out = document.createElement('div');
			out.className = 'editor-image-wrapper'
			const inner = document.createElement('img');
			inner.src = param;
			inner.className = 'editor-image';
			out.append(inner);
			blockContext = {
				type: 'image',
				source: param
			}
			break;
		case 'table':
			out = document.createElement('table');
			out.className = 'editor-table-wrapper';
			blockContext = {
				type: 'table',
				parser: param || 'csv',
				state: 0,
				body: undefined
			};
			break;
		default:
			out = undefined;
	}
	if (out) {
		editingBlock = out;
		out.setAttribute('data-compiled-from', line);
		out.classList.add('editor-block');
	}
	return out;
}

function terminateBlock() {
	editingBlock.setAttribute('data-block-context', JSON.stringify(blockContext));
	editingBlock.removeChild(appender);
	editor.append(appender);
	appender.focus();
	editingBlock = undefined;
	blockContext = undefined;
	insertingInBlock = false;
}

function parseEmptyLineInBlockContext(ctx) {
	let out;
	switch (pageBrakeState) {
		case 1:
			// end this block
			terminateBlock();
			pageBrakeState = 0;
			break;
		default:
			switch (ctx.type) {
				case 'code':
					// insert an empty line
					const codeLine = document.createElement('pre');
					codeLine.innerText = '\n';
					codeLine.setAttribute('data-compiled-from', "\n");
					editingBlock.insertBefore(codeLine, appender);
					out = true;
					break;
				case 'image':
					terminateBlock();
					pageBrakeState = 0;
					return true;
				case 'table':
					switch (ctx.state) {
						case 0: // table header
						case 1: // table content
							ctx.state = 1;
							return true;
						case 2: // table content cont.
							ctx.state = 3;
							return true;
						case 3: // caption
							terminateBlock();
							pageBrakeState = 0;
							return true;
					}
					break;
				default:
					break;
			}
			++pageBrakeState;
			break;
	}
	return out;
}

function parseLineInBlockContext(ctx, line) {
	let out = false;
	if (line.trim().length < 1) {
		return parseEmptyLineInBlockContext(ctx);
	} else {
		pageBrakeState = 0;
		// TODO
		switch (ctx.type) {
			case 'code':
				const codeLine = document.createElement('pre');
				codeLine.innerText = line;
				codeLine.setAttribute('data-compiled-from', line);
				editingBlock.insertBefore(codeLine, appender);
				out = true;
				break;
			case 'image':
				blockContext.caption = line;
				const caption = document.createElement('span');
				caption.className = 'editor-caption';
				caption.innerText = line;
				caption.setAttribute('data-compiled-from', line);
				editingBlock.insertBefore(caption, appender);
				out = true;
				terminateBlock();
				break;
			case 'table':
				if (ctx.state !== 3) {
					let row;
					switch (ctx.parser) {
						case 'csv':
						default:
							row = parseCSV(line);
							break;
						case 'tex':
							row = parseTeX(line);
							break;
					}
					row.setAttribute('data-compiled-from', line);
					if (ctx.state === 0) {
						const tableHeading = document.createElement('thead');
						tableHeading.className = 'editor-table-heading';
						tableHeading.append(row);
						out = tableHeading;
						ctx.state = 1;
					} else if (ctx.state === 1) {
						const tableBody = document.createElement('tbody');
						const id = randomString();
						tableBody.className = 'editor-table-body';
						tableBody.id = id;
						tableBody.append(row);
						ctx.body = id;
						ctx.state = 2;
						out = tableBody;
					} else {
						const tableBody = document.getElementById(ctx.body);
						tableBody.append(row);
						out = true;
					}
				} else {
					const tableCaption = document.createElement('span');
					tableCaption.className = 'editor-table-caption';
					tableCaption.innerText = line;
					out = tableCaption;
				}
				break;
			default:
				break;
		}
	}
	return out;
}

function parseLine(line) {
	let out;
	if (blockContext != null) {
		out = parseLineInBlockContext(blockContext, line);
	} else {
		if (line.trim().length < 1) {
			switch (pageBrakeState) {
				case 0:
					// insert section break
					out = document.createElement('span');
					out.className = 'editor-section-break';
					break;
				default:
					// ignore excessive enter presses
					break;
			}
			++pageBrakeState;
		} else {
			const trimmedInput = line.trim();
			out = document.createElement('div');
			pageBrakeState = 0;
			// formatting start!
			// rule 0. if in a block, then handle by the block
			if (blockContext != null) {
				out = parseLineInBlockContext(blockContext, line);
				return out != null;
			} else {
				if (trimmedInput.startsWith('#')) {
					// rule 1. any line starting with # is a title
					// count #
					let count = 0;
					for (let i = 0; i < trimmedInput.length; i++) {
						if (trimmedInput.charAt(i) !== '#') {
							break;
						} else {
							++count;
						}
					}
					const inner = document.createElement(`h${count}`);
					inner.innerText = trimmedInput.substr(count).trim();
					out.append(inner);
				} else if (trimmedInput.startsWith('@') || trimmedInput.startsWith('/')) {
					// rule 1'. any line starting with @ or / is a block
					// create a new block context
					let i;
					for (i = 0; i < trimmedInput.length; i++) {
						if (trimmedInput.charAt(i) === ' ') {
							break;
						}
					}
					const blockName = trimmedInput.substring(1, i);
					if (blockName.length < 1) {
						// a single @ symbol? wat?
						const inner = document.createElement('p');
						inner.innerText = '@';
						out.append(inner);
					} else {
						const blockParams = trimmedInput.substring(i);
						console.debug(blockName)
						out = startBlock(blockName, blockParams, line);
					}
					return out;
				} else {
					// rule 1'''. text in global formats simply
					// TODO: support more formatting options
					const inner = document.createElement('p');
					inner.innerText = line;
					out.append(inner);
				}
			}
		}
	}
	if (out && typeof out === 'object') {
		out.addEventListener('dblclick', onElementDoubleClick);
		out.setAttribute('data-compiled-from', line);
	}
	return out;
}

function submitInput() {
	const value = appender.value;
	const result = parseLine(value);
	if (result) {
		appender.value = '';
	}
	if (typeof result === 'object') {
		if (insertingInBlock) {
			editingBlock.insertBefore(result, appender);
		} else {
			editor.insertBefore(result, appender);
			if (editingBlock != null) {
				editor.removeChild(appender);
				editingBlock.append(appender);
				appender.focus();
				insertingInBlock = true;
			}
		}
	}
	appender.scrollIntoView();
}

function onFixerType(e) {
	if (e.key === 'Enter') {
		// TODO: submit last line and close
		// note that the line must NOT generate a block
	}
}

function onAppenderType(e) {
	if (e.key === 'Enter') {
		// submit last line
		submitInput();
	} else if (e.key === 'Backspace') {
		// remove/revert last line into editing mode
		if (appender.value.length < 1) {
			const sibling = appender.previousElementSibling;
			if (sibling) {
				if (sibling.hasAttribute('data-block-context')) {
					// if the last sibling is a block, then edit the block instead of deleting it
					editingBlock = sibling;
					blockContext = JSON.parse(sibling.getAttribute('data-block-context'));
					editor.removeChild(appender);
					editingBlock.append(appender);
					appender.focus();
				} else {
					appender.value = sibling.getAttribute('data-compiled-from');
					if (editingBlock == null) {
						editor.removeChild(sibling);
					} else {
						editingBlock.removeChild(sibling);
					}
				}
			} else {
				if (editingBlock != null) {
					// revert the block into line
					editingBlock.removeChild(appender);
					const nextSibling = editingBlock.nextSibling;
					if (nextSibling != null) {
						editor.insertBefore(appender, nextSibling);
					} else {
						editor.append(appender);
					}
					appender.value = editingBlock.getAttribute('data-compiled-from');
					appender.focus();
					editor.removeChild(editingBlock);
					editingBlock = undefined;
					blockContext = undefined;
				}
			}
			pageBrakeState = 0;
			e.preventDefault();
		}
	}
}

function onEditorClick(e) {
	appender.focus();
	e.preventDefault();
}

function onEditorLoseFocus() {
	console.log('blur');
}

function loadEditor(element) {
	element.classList.add('editor-wrapper');
	element.tabIndex = 0;
	element.addEventListener('mousedown', (e) => e.preventDefault());
	element.addEventListener('click', onEditorClick);
	element.addEventListener('blur', onEditorLoseFocus);
	element.append(appender);
	editor = element;
}

window.addEventListener('load', () => {
	const e = document.getElementById('editor');
	loadEditor(e);
	console.log('Ready');
});
